#pragma once

#include "thirdparty/lua-5.3.5/src/lua.hpp"
#include "lua_framework.hh"
#include "../../corelib/singleton.hpp"
#include "lua_helper.hh"
#include <unordered_map>
#include <unordered_set>
#include <list>
#include <memory>

// Bind C to Lua virtual machine
class LuaBinder {
    // Timer manager
    class TimerManager {
        using TimerCoMap = std::unordered_map<TimerHandle, lua_State*>;
        TimerCoMap timerCoMap_; // The map for timer ID and owner
    public:
        // Add timer
        // @param timerID timer ID
        // @param co Lua thread
        void addTimer(TimerID timerID, lua_State* co);
        // Remove timer
        // @param timerID timer ID
        // @return Lua thread
        lua_State* removeTimer(TimerID timerID);
        // Cleanup
        void clear();
    };
    // The manager for yield call
    class YieldCallManager {
    public:
        // Yield call
        struct YieldCall {
            google::protobuf::Message* ack; // Protobuf message
            CoInfo* coi; // Lua thread wrapper
            PathID pathID; // Path ID need to send ACK
            std::uint64_t uuid; // RPC UUID
            std::uint64_t rpcID; // RPC ID
            PathID fromPathID; // For entity RPC
            TimerHandle expiredTimerID; // Protection for expired RPC call
        };
    private:
        using YeildCallMap = std::unordered_map<lua_State*, YieldCall>;
        YeildCallMap yieldCallMap_; // The map for Lua thread and yield call
    public:
        // Add yield call
        // @param coi Lua thread wrapper
        // @param ack ACK message need to send back
        // @param pathID  Path ID waiting for ACK
        // @param uuid RPC UUID
        // @param rpcID RPC ID
        // @param fromPathID for entity RPC
        void add(
            CoInfo* coi,
            google::protobuf::Message* ack,
            PathID pathID,
            std::uint64_t uuid,
            std::uint64_t rpcID,
            PathID fromPathID
        );
        // Get yield call
        // @param L Lua thread
        // @return yield call
        YieldCall* get(lua_State* L);
        // Remove yield call
        // @param L Lua thread
        void remove(lua_State* L);
        // Cleanup
        void clear();
    };
    // The information of path finding
    struct PathFinder {
        lua_State* co; // Lua thread
    };
    // The manager of pending path finding
    class PathFinderManager {
        using PathFinderMap = std::unordered_map<std::uint32_t, PathFinder>;
        PathFinderMap pathFinderMap_; // Path finding map
        std::uint32_t finderSerial_; // The serial number for path finding
    public:
        // CTOR
        PathFinderManager();
        // Add a pending finding
        // @param siteType The site type of path finding
        // @param co Lua thread
        // @return The serial number of path finding
        std::uint32_t add(SiteType siteType, lua_State* co);
        // Remove pending finding
        // @param serial The serial number of path finding
        void remove(std::uint32_t serial);
        // Get a path finder by serial number
        // @param serial The serial number
        // @return PathFinder instance
        PathFinder* get(std::uint32_t serial);
        // Cleanup
        void clear();
    };
    // The information of pending entity finding
    struct EntityFinder {
        lua_State* co; // Lua thread
    };
    // Entity finder manager
    class EntityFinderManager {
        using EntityFinderMap = std::unordered_map<std::uint32_t, EntityFinder>;
        EntityFinderMap entityFinderMap_; // EntityFinder map
        std::uint32_t finderSerial_; // The serial number of pending finding
        std::uint64_t lastImmeEntityID_;
    public:
        // CTOR
        EntityFinderManager();
        // Add a pending finding
        // @param entityType The entity type of entity finding
        // @param co Lua thread
        // @return The serial number of entity finding
        std::uint32_t add(std::uint32_t entityType, lua_State* co);
        // Remove pending finding
        // @param serial The serial number of entity finding
        void remove(std::uint32_t serial);
        // Get a entity finder by serial number
        // @param serial The serial number
        // @return EntityFinder instance
        EntityFinder* get(std::uint32_t serial);
        // Cleanup
        void clear();
        // Set last immediately searching entity ID
        void setImmeEntityID(std::uint64_t entityID);
        // Returns last immediately searching entity ID
        std::uint64_t getImmeEntityID();
    };
    // Lua thread manager
    class CoManager {
        using ThreadMap = std::unordered_map<int, CoInfo*>;
        ThreadMap freeMap_; // Free thread
        ThreadMap busyMap_; // Busy thread
    public:
        // Add a busy thread
        void addBusy(CoInfo* coi);
        // Add a free thread and remove it from busy map
        void addFree(CoInfo* coi);
        // Get a free thread or fork a new one
        CoInfo* getFree();
        // Remove a thread
        void remove(CoInfo* coi);
        // Cleanup
        void clear();
    };
    // User Thread manager
    class UserCoManager {
        using ThreadMap = std::unordered_map<int, CoInfo*>;
        using StateMap = std::unordered_map<lua_State*, CoInfo*>;
        ThreadMap threadMap_;
        StateMap stateMap_;
    public:
        // Add a CoInfo
        void add(CoInfo* coi);
        // Remove a CoInfo
        void remove(CoInfo* coi);
        // Returns CoInfo by index
        CoInfo* getCoByRef(int ref, lua_State* L);
        // Returns CoInfo by lua_State address
        CoInfo* getCoByL(lua_State* L);
        // Returns the lua_State which resume from
        lua_State* getResumer(lua_State* L);
        // Cleanup
        void clear();
    };
    lua_State* virtualMachine_; // Lua virtual machine
    bool start_; // Start flag
    std::unique_ptr<CoInfo> mainCo_; // Main thread
    std::unique_ptr<CoInfo> startCo_; // Startup thread
    std::unique_ptr<CoInfo> tickCo_; // Tick thread
    //
    // Managers
    //
    PathFinderManager pathFinderManager_;
    YieldCallManager rpcCallManager_;
    TimerManager timer_;
    CoManager coManager_;
    EntityFinderManager entityFinderManager_;
    UserCoManager userCoManager_;

public:
    // Returns main thread
    lua_State* getMainThread() { return *mainCo_; }
    // Returns thread manager
    inline CoManager& getCoManager() { return coManager_; };
    // Returns path finder manager
    inline PathFinderManager& getPathFinderManager() { return pathFinderManager_; }
    // Returns yield RPC call manager
    inline YieldCallManager& getCallManager() { return rpcCallManager_; }
    // Returns timer manager
    inline TimerManager& getTimer() { return timer_; };
    // Returns entity finder manager
    inline EntityFinderManager& getEntityFinderManager() { return entityFinderManager_; }
    // Returns user thread manager
    inline UserCoManager& getUserCoManager() { return userCoManager_; };

public:
    // CTOR
    LuaBinder();
    // DTOR
    ~LuaBinder();
    // Returns Lua virtual machine
    lua_State* getVirtualMachine();
    //
    // Event handlers
    //
    bool onInstall();
    bool onUninstall();
    bool onStart();
    bool onStop();
    void onTick(time_t tick);
    void onPathJoin(PathID pathID, SiteType siteType, UserData userData);
    void onPathDisjoin(PathID pathID, SiteType siteType);
    void onFindPathTimeout(SiteType siteType, UserData userData);
    void onEntityFound(std::uint32_t type, const EntityIDVector& ids, std::uint64_t userData);

public:
    // Call RPC method in Lua VM
    // @param interfaceName The name of interface
    // @param methodName The method name of interface
    // @param pathID The path on locator
    // @param UUID RPC UUID
    // @param RPCID RPC method ID
    // @param req The request message
    // @param ack The response message for instant return
    bool callLuaImplMethod(
        const char* interfaceName,
        const char* methodName,
        PathID pathID,
        std::uint64_t uuid,
        std::uint64_t rpcID,
        const google::protobuf::Message& req,
        google::protobuf::Message& ack
    );

private:
    // Install Lua library and C library
    void installLibrary();

public:
    //
    // LOG
    //
    static int verbose(lua_State* L);
    static int debug(lua_State* L);
    static int warning(lua_State* L);
    static int error(lua_State* L);
    static int fatal(lua_State* L);
    //
    // Framework
    //
    static int sleep(lua_State* L);
    static int uuid(lua_State* L);
    static int findPath(lua_State* L);
    static int closePath(lua_State* L);
    static int findEntity(lua_State* L);
    //
    // RPC
    //
    static int ret(lua_State* L);
    static int abort(lua_State* L);
    //
    // Coroutine
    //
    static int create(lua_State* L);
    static int spawn(lua_State* L);
    static int yield(lua_State* L);
    static int resume(lua_State* L);
    static int close(lua_State* L);
};

#define LuaRef (*kratos::corelib::Singleton<LuaBinder>::instance())

